﻿namespace Chapter6

(*
2D Rendering with Sprite Batch ( * )

In this section we will add 2D rendering to display our asteroid field.
We will load some images (textures) that we have put in the Content project.
Then we will use the SpriteBatch class provided by XNA that greatly simplifies drawing in 2D.
*)
module SpriteBatch2D =

  open System
  open System.Collections.Generic
  open Microsoft.Xna.Framework
  open Microsoft.Xna.Framework.Graphics
  open Chapter3

  (*
  XNA has its own Vector2 definition.
  For this reason we define a conversion operator that given one of our Vector2<'u> turns it into an XNA Vector2.
  We will define an additional member of the Chapter2.Math.Vector2 with the "with" keyword.
  This means that the Chapter2.Math.Vector2 now has one member more, as if this member had been part of the original definition:
  *)
  type Chapter2.Math.Vector2< [<Measure>] 'u >
    with
      (*
      Since XNA uses 32-bit floating point numbers, we need to convert our coordinates, that are in 64-bit floating points numbers,
      with the float32 function.
      *)
      member this.ToXNAVector = Vector2(this.X |> float32, this.Y |> float32)

  (*
  We inherit the Microsoft.Xna.Framework.Game class as we did before.
  *)
  type Game2D() as this = 
    inherit Game()
    let graphics = new GraphicsDeviceManager(this)
    do graphics.GraphicsProfile <- GraphicsProfile.HiDef

    (*
    We declare two additional fields, sprite_batch and asteroid_texture.
    The sprite_batch allows us to render 2D items such as textures (images) and fonts.
    The texture is an image with a picture of an asteroid, originally in jpeg.
    *)
    let mutable sprite_batch : SpriteBatch = null
    let mutable asteroid_texture : Texture2D = null

    (*
    The main job of the Content project is converting various datafiles into formats that the graphics card understands and can use.
    Also, the Content project prepares our converted data so that loading them will be very quick and easy on our part.
    We use the Content field (of type ContentManager) of the game to access the data we wish to load.
    First, though, we need to tell our ContentManager that our Content project will put its converted files into a directory named "Content";
    this setting may be changed by modifying the properties of the GameHostContent project, and it becomes quite useful when we have more than one content project.
    *)
    do this.Content.RootDirectory <- "Content"

    let mutable asteroids = SmallAsteroidFieldSimulation.f0

    (*
    In the Initialize method we create our instance of a SpriteBatch.
    The SpriteBatch uses the graphics card to draw, through the GraphicsDevice of the game:
    *)
    override this.Initialize() = 
      sprite_batch <- new SpriteBatch(this.GraphicsDevice)    
      base.Initialize()
  
    (*
    In the LoadContent method we load the asteroid texture.
    The Content.Load method is actually a generic method, that is it has signature:
    Load<'a> : string -> 'a

    The important things to remember about the Content.Load method are two:
    - the string must represent a valid file in the Content project, without the extension
    - the generic type must be correct with the respect to the file being loaded (in our case, asteroid_texture has type Texture2D, which is appropriate to load the image contained in the "2D\Asteroid.jpg" file)
    *)
    override this.LoadContent() = 
      do asteroid_texture <- this.Content.Load(@"2D\Asteroid")
      base.LoadContent()

    (*
    The entire logic of the game resides in the Chapter4 namespace.
    It is always a very good idea to separate presentation and logic.
    The advantages to do so are innumerable; here are just a few:
    - we get cleaner code, easier to read and without mile-long source files
    - we get a better separation of concerns so that more people can work on the project without stepping on each other's toes
    - we can integrate our game logic with different rendering systems; here we are working on XNA, while in the next chapter we will use Unity and Mono
    *)
    override this.Update gt = 
      asteroids <- SmallAsteroidFieldSimulation.simulation_step asteroids
      base.Update gt

    (*
    First of all an important preliminary to drawing: we must clear the screen (any color will do), because otherwise we will get the trace of the previous frame's draw operations under the new draw operations.

    Drawing with the SpriteBatch is extremely simple.
    The SpriteBatch is a system that will receive a series of call to its two (overloaded) methods Draw and DrawString.
    These calls are not processed right away; rather, the SpriteBatch accumulates all those calls, sorts them appropriately and then draws everything.
    This delayed mechanism tends to produce faster rendering than the more naïve approach of rendering everything right when requested.
    We must signal the SpriteBatch the beginning and end of a drawing block, and we do so with the Begin and End methods.
    Only at the invocation of the End method does the SpriteBatch guarantee us that rendering will actually happen.
    *)
    override this.Draw gt = 
      this.GraphicsDevice.Clear(Color.Black)

      (*
      We start by opening the SpriteBatch; failure to do so will make the application crash:
      *)
      sprite_batch.Begin()

      (*
      We iterate all our asteroids and for each asteroid we draw it with the Draw call.
      We must scale the position of the asteroid from the original field (which is expressed in meters, millions of them) onto the screen (which is measured in 800x480 pixels):
      The Draw method of the SpriteBatch is overloaded, resulting in a series of methods that take the following information:
      - what texture to draw (texture parameter)
      - where to draw it on the screen and how large (position and scale parameters, or destinationRectangle parameter)
      - what portion of the texture to draw (sourceRectangle parameter)
      - how to tint the texture (color parameter)
      - how to rotate the texture (rotation parameter)
      - around which point to scale and rotate the texture (origin parameter)
      - what flipping effects to apply (effects parameter)
      - at what depth with respect to the viewer is the texture (layerDepth parameter)

      Each overload of the Draw method takes a subset of these information, setting reasonable default values for the unspecified parameters (such as no scale or rotation).
      The full list of overloads of the Draw method is the following (see also http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.spritebatch.draw.aspx):
      - SpriteBatch.Draw (Texture2D, Rectangle, Color)
      - SpriteBatch.Draw (Texture2D, Rectangle, Nullable<Rectangle>, Color)
      - SpriteBatch.Draw (Texture2D, Rectangle, Nullable<Rectangle>, Color, float32, Vector2, SpriteEffects, float32)
      - SpriteBatch.Draw (Texture2D, Vector2, Color)
      - SpriteBatch.Draw (Texture2D, Vector2, Nullable<Rectangle>, Color)
      - SpriteBatch.Draw (Texture2D, Vector2, Nullable<Rectangle>, Color, float32, Vector2, float32, SpriteEffects, float32)
      - SpriteBatch.Draw (Texture2D, Vector2, Nullable<Rectangle>, Color, float32, Vector2, Vector2, SpriteEffects, float32)
      *)
      let field_size = float32(Chapter3.SmallAsteroidFieldSimulation.field_size)
      for (a,i) in Seq.mapi (fun i a -> a,i) asteroids do
        do sprite_batch.Draw(
            asteroid_texture,
            Vector2(800.0f,480.0f) * a.Position.ToXNAVector / field_size, 
            Nullable<_>(),
            Color.White, 
            0.0f, 
            Vector2.Zero, 
            0.01f, 
            SpriteEffects.None,
            0.0f)

      (*
      After the last drawing operation we must invoke the End method.
      Failure to do so will cause the application to crash.
      *)
      sprite_batch.End()

      (*
      It is important to invoke the default Draw method of the Game class we have inherited, since there is some work it has to perform for us.
      *)
      base.Draw gt
